home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 April: Mac OS SDK / Dev.CD Apr 98 SDK2.toast / Development Kits (Disc 2) / QuickTime / Programming Stuff / Documentation / develop articles / develop Issue 24 / Sound Secrets / SoundSecrets.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-02-26  |  29.6 KB  |  1,113 lines  |  [TEXT/MMCC]

  1. /*
  2.     File:        SoundSecrets.c
  3.  
  4.     Contains:    A program to demonstrate cool things you can do with
  5.                 the Sound Manager.
  6.     
  7.     Written by:    Kip Olson, Apple Computer
  8. */
  9.  
  10. #include <Types.h>
  11. #include <Memory.h>
  12. #include <Quickdraw.h>
  13. #include <TextEdit.h>
  14. #include <Dialogs.h>
  15. #include <Packages.h>
  16. #include <OSUtils.h>
  17. #include <ToolUtils.h>
  18. #include <Menus.h>
  19. #include <Fonts.h>
  20. #include <Events.h>
  21. #include <Resources.h>
  22. #include <Files.h>
  23. #include <Errors.h>
  24. #include <Strings.h>
  25. #include <Sound.h>
  26. #include <SoundComponents.h>
  27. #include <SoundInput.h>
  28. #include <stddef.h>
  29. #include <stdio.h>
  30.  
  31. // 'DITL' ID's for main dialog
  32.  
  33. enum {
  34.     kQuitButton = 1,
  35.     kPlayButton,
  36.     kStopButton,
  37.     kLeftVolumeSlider,
  38.     kLeftVolumeEdit,
  39.     kRightVolumeSlider,
  40.     kRightVolumeEdit,
  41.     kRateSlider,
  42.     kRateEdit,
  43.     kSizeSlider,
  44.     kSizeEdit,
  45.     kLerpCheckBox,
  46.     kHardwareSettingsLabel,
  47.     kSoundSettingsLabel
  48. };
  49.  
  50. // constants
  51.  
  52. enum {
  53.     kDialogResID = 128,
  54.  
  55.     kRequiredSndMgrMajorRev = 0x03,                    // Sound Manager 3.1 or later required
  56.     kRequiredSndMgrMinorRev = 0x10,                    
  57.  
  58.     kMinChunkSamples =    128,                        // smallest buffer size (in samples)
  59.     kVolumeSliderMax =    0x0200,                        // largest volume slider value (2.0)
  60.     kVolumeEditMax =    0x0A00,                        // largest volume edit value (10.0)
  61.     kRateSliderMax =    0x00020000,                    // largest rate multiplier slider value (2.0)
  62.     kRateEditMax =        0x000A0000                    // largest rate multiplier edit value (10.0)
  63. };
  64.  
  65. // sound header data structure
  66.  
  67. typedef union {
  68.     SoundHeader        s;                                // plain sound header
  69.     CmpSoundHeader    c;                                // compressed sound header
  70.     ExtSoundHeader    e;                                // extended sound header
  71. } CommonSoundHeader, *CommonSoundHeaderPtr;
  72.  
  73. // globals data structure
  74.  
  75. typedef struct {
  76.     CommonSoundHeader    sndHeader;                    // sound header
  77.     SoundComponentData    sndInfo;                    // sound info
  78.     CompressionInfo        compInfo;                    // compression info
  79.     SoundComponentData    hardwareInfo;                // hardware info
  80.     SndChannelPtr        sndChannel;                    // sound channel
  81.     Handle                sndH;                        // 'snd ' resource handle
  82.     
  83.     unsigned long        headerOffset;                // offset to sound header in handle
  84.     unsigned long        dataOffset;                    // offset of audio samples in handle
  85.     unsigned long        totalSamples;                // total audio samples in handle
  86.     unsigned long        sampleOffset;                // current sample offset in handle
  87.     UnsignedFixed        rateMultiplier;                // current rate multiplier
  88.     unsigned short        leftVolume;                    // current left volume
  89.     unsigned short        rightVolume;                // current right volume
  90.     unsigned long        chunkSamples;                // current buffer size
  91.     unsigned long        minChunkSamples;            // minimum buffer size
  92. } GlobalsRecord, *GlobalsPtr;
  93.  
  94. // globals
  95.  
  96. GlobalsRecord    myGlobals;                            // globals used by our window
  97.  
  98. // prototypes
  99.  
  100. void            main(void);
  101. Boolean            HasValidSoundManager(void);
  102. OSErr            FindMatchingSound(GlobalsPtr globals);
  103.  
  104. OSErr            ParseSnd(Handle sndH, SoundComponentData *sndInfo, CompressionInfo *compInfo,
  105.                          unsigned long *headerOffsetResult, unsigned long *dataOffsetResult);
  106. void            SetupDialog(GlobalsPtr globals, DialogPtr dialog);
  107. OSErr            GetCompressionName(OSType compressionType, Str255 compressionName);
  108. void            SoundSettings2String(SoundComponentData *sndInfo, Str255 stringData);
  109.  
  110. pascal void        DoCallback(SndChannelPtr chan, SndCommand *cmd);
  111. OSErr            DoPlay(GlobalsPtr globals);
  112. OSErr            DoStop(SndChannelPtr chan);
  113.  
  114. OSErr            GetHardwareSettings(SndChannelPtr chan, SoundComponentData *hardwareInfo);
  115. OSErr            SetRateMultiplier(SndChannelPtr chan, UnsignedFixed rateMultiplier);
  116. OSErr            GetRateMultiplier(SndChannelPtr chan, UnsignedFixed *rateMultiplier);
  117. OSErr            SetVolume(SndChannelPtr chan, unsigned short leftVolume, unsigned short rightVolume);
  118. OSErr            GetVolume(SndChannelPtr chan, unsigned short *leftVolume, unsigned short *rightVolume);
  119. OSErr            SetLinearInterpolation(SndChannelPtr chan, short doLerp);
  120.  
  121. void            UpdateSlider(GlobalsPtr globals, DialogPtr dialog, short item);
  122. void            DragSlider(GlobalsPtr globals, DialogPtr dialog, short item);
  123. pascal void        DrawSlider(WindowPtr dialog, short item);
  124. void            GetThumbRect(GlobalsPtr globals, short item, Rect *bounds, Rect *thumbRect);
  125.  
  126. ControlHandle    GetItemHandle(DialogPtr dialog, short item);
  127. void            SetItemProc(DialogPtr dialog, short item, UserItemUPP proc);
  128. void            GetItemRect(DialogPtr dialog, short item, Rect *iRect);
  129. short            GetCheckBox(DialogPtr dialog, short item);
  130. void            SetCheckBox(DialogPtr dialog, short item, short value);
  131.  
  132. extern pascal OSErr SndGetInfo(SndChannelPtr chan, OSType selector, void *infoPtr)
  133.  FOURWORDINLINE(0x203C, 0x063C, 0x0018, 0xA800);
  134.  
  135. /*    main entry point
  136.  
  137.     Remember, this application is meant to show cool things you can do with the
  138.     Sound Manager, not to show good human interface programming. The following
  139.     code to manage the dialog is not meant to be a good example of application
  140.     design.
  141. */
  142.  
  143. void main(void)
  144. {
  145.     OSErr            err;
  146.     DialogPtr        dialog;
  147.     GrafPtr            oldPort;
  148.     short            item;
  149.     Boolean            done;
  150.     Str255            stringData;
  151.     GlobalsPtr        globals = &myGlobals;
  152.     ControlHandle    itemHandle;
  153.     float            rate;
  154.     unsigned short    volume;
  155.     unsigned long    chunkSamples;
  156.     short            lerpOn;
  157.     NumVersion        version;
  158.  
  159.     // initialize the world
  160.  
  161.     InitGraf((Ptr) &qd.thePort);
  162.     InitFonts();
  163.     InitWindows();
  164.     InitMenus();
  165.     TEInit();
  166.     InitDialogs(nil);
  167.     InitCursor();
  168.     MaxApplZone();
  169.  
  170.     // make sure we have Sound Manager 3.1
  171.  
  172.     if (!HasValidSoundManager())
  173.     {
  174.         err = noHardwareErr;
  175.         goto SndVersionFailed;
  176.     }
  177.  
  178.     // open a Sound Manager channel
  179.  
  180.     globals->sndChannel = nil;                                                    // tell Sound Manager to allocate the sound channel structure
  181.     err = SndNewChannel(&globals->sndChannel, sampledSynth, 0, DoCallback);        // allocate a sound channel with a callback routine
  182.     if (err != noErr)
  183.         goto SndNewChannelFailed;
  184.  
  185.     // get the sound hardware settings
  186.  
  187.     err = GetHardwareSettings(globals->sndChannel, &globals->hardwareInfo);
  188.     if (err != noErr)
  189.         goto GetSettingsFailed;
  190.  
  191.     // find a sound resource that matches these hardware settings
  192.  
  193.     err = FindMatchingSound(globals);
  194.     if (err != noErr)
  195.         goto MatchingSoundNotFound;
  196.  
  197.     // setup globals to play this sound
  198.  
  199.     HLock(globals->sndH);
  200.     globals->sndHeader = *((CommonSoundHeaderPtr) (*globals->sndH + globals->headerOffset));    // use sound header from resource
  201.     globals->sndHeader.s.loopStart = 0;                                            // clear loop points
  202.     globals->sndHeader.s.loopEnd = 0;
  203.  
  204.     GetRateMultiplier(globals->sndChannel, &globals->rateMultiplier);            // get initial rate multiplier setting
  205.     GetVolume(globals->sndChannel, &globals->leftVolume, &globals->rightVolume);// get initial volume settings
  206.     globals->sampleOffset = 0;                                                    // start playing at first sample
  207.     globals->totalSamples = globals->sndInfo.sampleCount;                        // total no. samples in sound
  208.     globals->chunkSamples = globals->totalSamples;                                // make chunk the size of the sound
  209.  
  210.     // If the chunk size is too small, the Sound Manager will spend too much time
  211.     // servicing the interrupt, so we need to limit the size of the smallest chunk.
  212.     // We do this by quantizing a constant to a multiple of the packet size of the
  213.     // sound and pinning the chunk size to this minimum.
  214.  
  215.     globals->minChunkSamples = (kMinChunkSamples / globals->compInfo.samplesPerPacket) * globals->compInfo.samplesPerPacket;
  216.  
  217.     // setup main window
  218.  
  219.     GetPort(&oldPort);
  220.  
  221.     dialog = GetNewDialog(kDialogResID, nil, (WindowPtr) -1);
  222.     if (dialog == nil)
  223.     {
  224.         err = paramErr;
  225.         goto NewDialogFailed;
  226.     }
  227.  
  228.     SetPort(dialog);
  229.     SetupDialog(globals, dialog);
  230.     ShowWindow(dialog);
  231.  
  232.     // main event loop
  233.  
  234.     done = false;
  235.     while (!done)
  236.     {
  237.         ModalDialog(nil, &item);
  238.  
  239.         switch (item)
  240.         {
  241.             case kQuitButton:
  242.                 done = true;
  243.                 break;
  244.  
  245.             case kPlayButton:
  246.                 DoStop(globals->sndChannel);
  247.                 globals->sampleOffset = 0;
  248.                 DoPlay(globals);
  249.                 break;
  250.  
  251.             case kStopButton:
  252.                 DoStop(globals->sndChannel);                    
  253.                 break;
  254.  
  255.             case kLeftVolumeSlider:
  256.             case kRightVolumeSlider:
  257.             case kRateSlider:
  258.             case kSizeSlider:
  259.                 DragSlider(globals, dialog, item);
  260.                 break;
  261.  
  262.             case kLeftVolumeEdit:
  263.             case kRightVolumeEdit:
  264.                 itemHandle = GetItemHandle(dialog, item);
  265.                 GetIText((Handle) itemHandle, stringData);
  266.  
  267.                 p2cstr(stringData);
  268.                 sscanf((char *) stringData, "%f", &rate);
  269.                 volume = rate * 256L;
  270.                 if (volume > kVolumeEditMax)
  271.                     volume = kVolumeEditMax;
  272.  
  273.                 if (item == kLeftVolumeEdit)
  274.                 {
  275.                     globals->leftVolume = volume;
  276.                     UpdateSlider(globals, dialog, kLeftVolumeSlider);
  277.                 }
  278.                 else
  279.                 {
  280.                     globals->rightVolume = volume;
  281.                     UpdateSlider(globals, dialog, kRightVolumeSlider);
  282.                 }
  283.  
  284.                 SetVolume(globals->sndChannel, globals->leftVolume, globals->rightVolume);                    
  285.                 break;
  286.  
  287.             case kRateEdit:
  288.                 itemHandle = GetItemHandle(dialog, kRateEdit);
  289.                 GetIText((Handle) itemHandle, stringData);
  290.  
  291.                 p2cstr(stringData);
  292.                 sscanf((char *) stringData, "%f", &rate);
  293.                 globals->rateMultiplier = rate * 65536L;
  294.                 if (globals->rateMultiplier > kRateEditMax)
  295.                     globals->rateMultiplier = kRateEditMax;
  296.  
  297.                 SetRateMultiplier(globals->sndChannel, globals->rateMultiplier);                    
  298.                 UpdateSlider(globals, dialog, kRateSlider);
  299.                 break;
  300.  
  301.             case kSizeEdit:
  302.                 itemHandle = GetItemHandle(dialog, kSizeEdit);
  303.                 GetIText((Handle) itemHandle, stringData);
  304.                 StringToNum(stringData, (long *) &chunkSamples);
  305.  
  306.                 chunkSamples = (chunkSamples / globals->compInfo.samplesPerPacket) * globals->compInfo.samplesPerPacket;
  307.                 if (chunkSamples < globals->minChunkSamples)
  308.                     chunkSamples = globals->minChunkSamples;
  309.                 else if (chunkSamples > globals->totalSamples)
  310.                     chunkSamples = globals->totalSamples;
  311.  
  312.                 globals->chunkSamples = chunkSamples;        // change value all at once 'cause it's used at interrupt time
  313.                 UpdateSlider(globals, dialog, kSizeSlider);
  314.                 break;
  315.  
  316.             case kLerpCheckBox:
  317.                 lerpOn = GetCheckBox(dialog, kLerpCheckBox);
  318.                 lerpOn = (lerpOn) ? 0 : 1;
  319.  
  320.                 SetLinearInterpolation(globals->sndChannel, lerpOn);
  321.                 SetCheckBox(dialog, kLerpCheckBox, lerpOn);
  322.                 break;
  323.         }
  324.     }
  325.  
  326.     DoStop(globals->sndChannel);
  327.  
  328.     DisposeDialog(dialog);
  329.     SetPort(oldPort);
  330.  
  331. NewDialogFailed:
  332.     HUnlock(globals->sndH);
  333. MatchingSoundNotFound:
  334. GetSettingsFailed:
  335.     SndDisposeChannel(globals->sndChannel, true);
  336. SndNewChannelFailed:
  337. SndVersionFailed:
  338.     return;
  339. }
  340.  
  341. /*    HasValidSoundManager
  342.  
  343.     This routine checks the Sound Manager version and makes sure the version is greater
  344.     than or equal to the version needed for this program to run. It returns true if
  345.     the version is ok, and false it it is not.
  346. */
  347.  
  348.  
  349. Boolean HasValidSoundManager(void)
  350. {
  351.     NumVersion   version;
  352.  
  353.     version = SndSoundManagerVersion();                            // get the Sound Manager version
  354.  
  355.     if ((version.majorRev > kRequiredSndMgrMajorRev) ||            // make sure this version is >= version we need
  356.         ((version.majorRev == kRequiredSndMgrMajorRev) && (version.minorAndBugRev >= kRequiredSndMgrMinorRev)))
  357.         return (true);
  358.     else
  359.         return (false);
  360. }
  361.  
  362. /*    ParseSnd
  363.  
  364.     This routine parses an 'snd ' resource and returns information about the sound format.
  365.     It uses GetSoundHeaderOffset to find the sound header data structure in the resource,
  366.     parses the sound header and then uses GetCompressionInfo to get the data format and
  367.     convert frames into samples. It returns the sound and compression info, as well as
  368.     offsets in the source resource of the sound header and the start of the audio sample data.
  369. */
  370.  
  371. OSErr ParseSnd(Handle sndH, SoundComponentData *sndInfo, CompressionInfo *compInfo,
  372.                unsigned long *headerOffsetResult, unsigned long *dataOffsetResult)
  373. {
  374.     CommonSoundHeaderPtr    sh;
  375.     unsigned long            headerOffset, dataOffset;
  376.     short                    compressionID;
  377.     OSErr                    err;
  378.  
  379.     // use GetSoundHeaderOffset to find the offset of sound header
  380.     // from the beginning of the sound resource handle
  381.  
  382.     err = GetSoundHeaderOffset((SndListHandle) sndH, (long *) &headerOffset);
  383.     if (err != noErr)
  384.         return (err);
  385.  
  386.     // get pointer to sound header using this offset
  387.  
  388.     sh = (CommonSoundHeaderPtr) (*sndH + headerOffset);
  389.     dataOffset = headerOffset;
  390.  
  391.     // extract the sound info based on encode type
  392.  
  393.     switch (sh->s.encode)
  394.     {
  395.         case stdSH:
  396.             sndInfo->sampleCount = sh->s.length;
  397.             sndInfo->sampleRate = sh->s.sampleRate;
  398.             sndInfo->sampleSize = 8;
  399.             sndInfo->numChannels = 1;
  400.             dataOffset += offsetof(SoundHeader, sampleArea);
  401.             compressionID = notCompressed;
  402.             break;
  403.  
  404.         case extSH:
  405.             sndInfo->sampleCount = sh->e.numFrames;
  406.             sndInfo->sampleRate = sh->e.sampleRate;
  407.             sndInfo->sampleSize = sh->e.sampleSize;
  408.             sndInfo->numChannels = sh->e.numChannels;
  409.             dataOffset += offsetof(ExtSoundHeader, sampleArea);
  410.             compressionID = notCompressed;
  411.             break;
  412.  
  413.         case cmpSH:
  414.             sndInfo->sampleCount = sh->c.numFrames;
  415.             sndInfo->sampleRate = sh->c.sampleRate;
  416.             sndInfo->sampleSize = sh->c.sampleSize;
  417.             sndInfo->numChannels = sh->c.numChannels;
  418.             dataOffset += offsetof(CmpSoundHeader, sampleArea);
  419.             compressionID = sh->c.compressionID;
  420.             sndInfo->format = sh->c.format;
  421.             break;
  422.  
  423.         default:
  424.             return (badFormat);
  425.             break;
  426.     }
  427.  
  428.     // use GetCompressionInfo to get the data format of the
  429.     // sound and the compression information
  430.  
  431.     compInfo->recordSize = sizeof(CompressionInfo);
  432.     err = GetCompressionInfo(compressionID, sndInfo->format, sndInfo->numChannels,
  433.                              sndInfo->sampleSize, compInfo);
  434.     if (err != noErr)
  435.         return (err);
  436.  
  437.     // store the sound data format and convert frames to samples
  438.  
  439.     sndInfo->format = compInfo->format;
  440.     sndInfo->sampleCount *= compInfo->samplesPerPacket;
  441.  
  442.     // return offset of header and audio data
  443.  
  444.     *headerOffsetResult = headerOffset;
  445.     *dataOffsetResult = dataOffset;
  446.  
  447.     return (noErr);
  448. }
  449.  
  450. /*    GetHardwareSettings
  451.  
  452.     This routine uses the SndGetInfo call to return information about the
  453.     sound hardware, including number of channels, sampling rate and sample size.
  454.     It returns this information in a SoundComponentData structure.
  455. */
  456.  
  457. OSErr GetHardwareSettings(SndChannelPtr chan, SoundComponentData *hardwareInfo)
  458. {
  459.     OSErr    err;
  460.  
  461.     err = SndGetInfo(chan, siNumberChannels, &hardwareInfo->numChannels);
  462.     if (err != noErr)
  463.         return (err);
  464.  
  465.     err = SndGetInfo(chan, siSampleRate, &hardwareInfo->sampleRate);
  466.     if (err != noErr)
  467.         return (err);
  468.  
  469.     err = SndGetInfo(chan, siSampleSize, &hardwareInfo->sampleSize);
  470.     if (err != noErr)
  471.         return (err);
  472.  
  473.     if (hardwareInfo->sampleSize == 8)
  474.         hardwareInfo->format = kOffsetBinary;
  475.     else
  476.         hardwareInfo->format = kTwosComplement;
  477.  
  478.     return (noErr);
  479. }
  480.  
  481. /*    FindMatchingSound
  482.  
  483.     This routines tries to find a sound resource whose format matches the
  484.     current sound hardware settings. If a match is found, the globals are
  485.     set up to describe the sound. A sound that matches the hardware settings
  486.     exactly will play most efficiently, requiring less of a CPU load. 
  487. */
  488.  
  489. OSErr FindMatchingSound(GlobalsPtr globals)
  490. {
  491.     OSErr        err;
  492.     short        index;
  493.     
  494.     // loop over all sound resources in this application
  495.  
  496.     index = 1;
  497.     while (globals->sndH = Get1IndResource('snd ', index))
  498.     {
  499.         // parse sound resource to get information about the audio data
  500.  
  501.         err = ParseSnd(globals->sndH, &globals->sndInfo, &globals->compInfo, &globals->headerOffset, &globals->dataOffset);
  502.         if (err != noErr)
  503.             continue;
  504.     
  505.         // compare sample size and number of channels of this sound to the hardware settings
  506.  
  507.         if ((globals->sndInfo.sampleSize == globals->hardwareInfo.sampleSize) &&
  508.             (globals->sndInfo.numChannels == globals->hardwareInfo.numChannels))
  509.         {
  510.             return (noErr);                            // found a match, so use this sound
  511.         }
  512.  
  513.         // match not found, so release this resource and get the next one
  514.  
  515.         ReleaseResource(globals->sndH);
  516.         index += 1;
  517.     }
  518.  
  519.     return (resNotFound);                            // no matching resource found
  520. }    
  521.  
  522. /*    GetCompressionName
  523.  
  524.     This routine returns the name of the audio decompression codec specified
  525.     by the OSType parameter. It calls the Component Manager for the codec name
  526.     and returns it as a string. If this codec type is not found, an error will be
  527.     returned.
  528. */
  529.  
  530. OSErr GetCompressionName(OSType compressionType, Str255 compressionName)
  531. {
  532.     ComponentDescription    cd;
  533.     Component                component;
  534.     Handle                    componentName;
  535.     OSErr                    err;
  536.  
  537.     // look for decompressor component
  538.  
  539.     cd.componentType = kSoundDecompressor;
  540.     cd.componentSubType = compressionType;
  541.     cd.componentManufacturer = 0;
  542.     cd.componentFlags = 0;
  543.     cd.componentFlagsMask = 0;
  544.  
  545.     component = FindNextComponent(nil, &cd);
  546.     if (component == nil)
  547.     {
  548.         err = siInvalidCompression;
  549.         goto FindComponentFailed;
  550.     }
  551.  
  552.     // create handle for name
  553.  
  554.     componentName = NewHandle(0);
  555.     if (componentName == nil)
  556.     {
  557.         err = MemError();
  558.         goto NewNameFailed;
  559.     }
  560.  
  561.     // get name from Component Manager
  562.  
  563.     err = GetComponentInfo(component, &cd, componentName, nil, nil);
  564.     if (err != noErr)
  565.         goto GetInfoFailed;
  566.  
  567.     // return name
  568.  
  569.     BlockMoveData(*componentName, compressionName, GetHandleSize(componentName));
  570.  
  571. GetInfoFailed:
  572.     DisposeHandle(componentName);
  573. NewNameFailed:
  574. FindComponentFailed:
  575.     return (err);
  576. }
  577.  
  578. /*    DoCallback
  579.  
  580.     This Pascal-type routine is called when the Sound Manager executes a callBackCmd.
  581.     We passed a pointer to this routine when we opened the channel with SndNewChannel.
  582.     We are passed the sound channel and the sound command used when we issued the
  583.     callBackCmd. The param2 field of this command contains a user-defined value, into
  584.     which we have placed a pointer to our globals. We use this pointer to call DoPlay,
  585.     which actually handles the interrupt.
  586.     
  587.     Remember, this routine is called at interrupt time, so A5 is not
  588.     set up and you cannot call the Memory Manager or most other toolbox routines.
  589. */
  590.  
  591. pascal void DoCallback(SndChannelPtr chan, SndCommand *cmd)
  592. {
  593.     DoPlay((GlobalsPtr) cmd->param2);
  594. }
  595.  
  596. /*    DoPlay
  597.  
  598.     This routine uses a bufferCmd/callBackCmd scheme to play consecutive chunks a
  599.     sound resource. If the end of the sound data is reached, playback wraps around
  600.     to the beginning of the sound.
  601.     
  602.     This routine is called once at non-interrupt time to start the sound playing,
  603.     and then will be called repeatedly at interrupt time in response to the callBackCmd
  604.     (as described above in the DoCallback routine). For this reason, we cannot assume A5
  605.     will be set up, nor can we call most toolbox routines. A pointer to our globals is
  606.     passed in so we can keep track of things.
  607. */
  608.  
  609. OSErr DoPlay(GlobalsPtr globals)
  610. {
  611.     CommonSoundHeaderPtr    sh;
  612.     SndCommand                sndCmd;
  613.     unsigned long            chunkSamples, frames;
  614.     Ptr                        samplePtr;
  615.     OSErr                    err;
  616.  
  617.     // wrap around to beginning of buffer if end has been reached
  618.  
  619.     if (globals->sampleOffset == globals->totalSamples)
  620.         globals->sampleOffset = 0;
  621.  
  622.     // determine size of chunk to play, in samples
  623.  
  624.     chunkSamples = globals->totalSamples - globals->sampleOffset;
  625.     if (chunkSamples > globals->chunkSamples)
  626.         chunkSamples = globals->chunkSamples;
  627.  
  628.     // calculate pointer to audio data by converting the sample offset to bytes
  629.     // and adding it to address of the beginning of the audio samples.
  630.  
  631.     samplePtr = *globals->sndH + globals->dataOffset;
  632.     samplePtr += (globals->sampleOffset / globals->compInfo.samplesPerPacket) * globals->compInfo.bytesPerFrame;
  633.     globals->sampleOffset += chunkSamples;
  634.  
  635.     // setup fields in sound header for the address of the audio samples and
  636.     // the number of frames.
  637.  
  638.     frames = chunkSamples / globals->compInfo.samplesPerPacket;
  639.  
  640.     switch (globals->sndHeader.s.encode)
  641.     {
  642.         case stdSH:
  643.             globals->sndHeader.s.samplePtr = samplePtr;
  644.             globals->sndHeader.s.length = frames;
  645.             break;
  646.  
  647.         case extSH:
  648.             globals->sndHeader.e.samplePtr = samplePtr;
  649.             globals->sndHeader.e.numFrames = frames;
  650.             break;
  651.  
  652.         case cmpSH:
  653.             globals->sndHeader.c.samplePtr = samplePtr;
  654.             globals->sndHeader.c.numFrames = frames;
  655.             break;
  656.     }
  657.  
  658.     // issue bufferCmd to play the sound, using SndDoImmediate
  659.  
  660.     sndCmd.cmd = bufferCmd;
  661.     sndCmd.param1 = 0;
  662.     sndCmd.param2 = (long) &globals->sndHeader;
  663.     
  664.     err = SndDoImmediate(globals->sndChannel, &sndCmd);
  665.     if (err != noErr)
  666.         return (err);
  667.     
  668.     // issue callBackCmd using SndDoCommand so we get called
  669.     // back when the buffer is done playing.
  670.  
  671.     sndCmd.cmd = callBackCmd;
  672.     sndCmd.param1 = 0;
  673.     sndCmd.param2 = (long) globals;
  674.  
  675.     err = SndDoCommand(globals->sndChannel, &sndCmd, true);
  676.     return (err);
  677. }
  678.  
  679. /*    DoStop
  680.  
  681.     This routine is used to stop sound playing on the channel. It is important
  682.     to flush the queue before sending the quietCmd, because any commands left
  683.     in the queue will begin executing when the quietCmd is received.
  684. */
  685.  
  686. OSErr DoStop(SndChannelPtr chan)
  687. {
  688.     SndCommand        sndCmd;
  689.     OSErr            err;
  690.  
  691.     sndCmd.cmd = flushCmd;
  692.     sndCmd.param1 = 0;
  693.     sndCmd.param2 = 0;
  694.     
  695.     err = SndDoImmediate(chan, &sndCmd);
  696.     if (err != noErr)
  697.         return (err);
  698.  
  699.     sndCmd.cmd = quietCmd;
  700.     sndCmd.param1 = 0;
  701.     sndCmd.param2 = 0;
  702.     
  703.     err = SndDoImmediate(chan, &sndCmd);
  704.     return (err);
  705. }
  706.  
  707. /*    SetRateMultiplier
  708.  
  709.     This routine sets the rate multiplier for a channel.
  710. */
  711.  
  712. OSErr SetRateMultiplier(SndChannelPtr chan, UnsignedFixed rateMultiplier)
  713. {
  714.     SndCommand        sndCmd;
  715.     OSErr            err;
  716.  
  717.     sndCmd.cmd = rateMultiplierCmd;
  718.     sndCmd.param1 = 0;
  719.     sndCmd.param2 = rateMultiplier;
  720.     
  721.     err = SndDoImmediate(chan, &sndCmd);
  722.     return (err);
  723. }
  724.  
  725. /*    GetRateMultiplier
  726.  
  727.     This routine returns the rate multiplier for a channel.
  728. */
  729.  
  730. OSErr GetRateMultiplier(SndChannelPtr chan, UnsignedFixed *rateMultiplier)
  731. {
  732.     SndCommand        sndCmd;
  733.     OSErr            err;
  734.  
  735.     sndCmd.cmd = getRateMultiplierCmd;
  736.     sndCmd.param1 = 0;
  737.     sndCmd.param2 = (long) rateMultiplier;
  738.     
  739.     err = SndDoImmediate(chan, &sndCmd);
  740.     return (err);
  741. }
  742.  
  743. /*    SetVolume
  744.  
  745.     This routine sets the left and right volumes for a channel.
  746. */
  747.  
  748. OSErr SetVolume(SndChannelPtr chan, unsigned short leftVolume, unsigned short rightVolume)
  749. {
  750.     SndCommand        sndCmd;
  751.     OSErr            err;
  752.  
  753.     sndCmd.cmd = volumeCmd;
  754.     sndCmd.param1 = 0;
  755.     sndCmd.param2 = (rightVolume << 16) | leftVolume;
  756.     
  757.     err = SndDoImmediate(chan, &sndCmd);
  758.     return (err);
  759. }
  760.  
  761. /*    GetVolume
  762.  
  763.     This routine returns the left and right volumes for a channel.
  764. */
  765.  
  766. OSErr GetVolume(SndChannelPtr chan, unsigned short *leftVolume, unsigned short *rightVolume)
  767. {
  768.     SndCommand        sndCmd;
  769.     unsigned long    volume;
  770.     OSErr            err;
  771.  
  772.     sndCmd.cmd = getVolumeCmd;
  773.     sndCmd.param1 = 0;
  774.     sndCmd.param2 = (long) &volume;
  775.     
  776.     err = SndDoImmediate(chan, &sndCmd);
  777.     
  778.     if (err == noErr)
  779.     {
  780.         *leftVolume = volume & 0x0000FFFF;
  781.         *rightVolume = volume >> 16;
  782.     }
  783.     return (err);
  784. }
  785.  
  786. /*    SetLinearInterpolation
  787.  
  788.     This routine turns linear interpolation rate conversion on and off for a channel.
  789. */
  790.  
  791. OSErr SetLinearInterpolation(SndChannelPtr chan, short doLerp)
  792. {
  793.     SndCommand        sndCmd;
  794.     OSErr            err;
  795.  
  796.     sndCmd.cmd = reInitCmd;
  797.     sndCmd.param1 = 0;
  798.     sndCmd.param2 = (doLerp) ? 0 : initNoInterp;
  799.     
  800.     err = SndDoImmediate(chan, &sndCmd);
  801.     return (err);
  802. }
  803.  
  804. /*    Dialog Routines
  805.  
  806.     Remember, this application is meant to show cool things you can do with the
  807.     Sound Manager, not to show good human interface programming. The following
  808.     code to manage the dialog and handle the sliders is pretty crude, and is not
  809.     recommended for general application use. It is definitely not localizable!
  810. */
  811.  
  812. void SetupDialog(GlobalsPtr globals, DialogPtr dialog)
  813. {
  814.     Str255            stringData;
  815.     ControlHandle    itemHandle;
  816.     OSErr            err;
  817.  
  818.     /* setup left volume edit box and slider */
  819.     
  820.     sprintf((char *) stringData, "%7.5f", ((float) globals->leftVolume) / 256.0);
  821.     c2pstr((char *) stringData);
  822.     itemHandle = GetItemHandle(dialog, kLeftVolumeEdit);
  823.     SetIText((Handle) itemHandle, stringData);
  824.     SetItemProc(dialog, kLeftVolumeSlider, DrawSlider);
  825.  
  826.     /* setup right volume edit box and slider */
  827.     
  828.     sprintf((char *) stringData, "%7.5f", ((float) globals->rightVolume) / 256.0);
  829.     c2pstr((char *) stringData);
  830.     itemHandle = GetItemHandle(dialog, kRightVolumeEdit);
  831.     SetIText((Handle) itemHandle, stringData);
  832.     SetItemProc(dialog, kRightVolumeSlider, DrawSlider);
  833.  
  834.     /* setup rate multiplier edit box and slider */
  835.     
  836.     sprintf((char *) stringData, "%7.5f", ((float) globals->rateMultiplier) / 65536.0);
  837.     c2pstr((char *) stringData);
  838.     itemHandle = GetItemHandle(dialog, kRateEdit);
  839.     SetIText((Handle) itemHandle, stringData);
  840.     SetItemProc(dialog, kRateSlider, DrawSlider);
  841.  
  842.     /* setup buffer size edit box and slider */
  843.  
  844.     NumToString(globals->chunkSamples, stringData);
  845.     itemHandle = GetItemHandle(dialog, kSizeEdit);
  846.     SetIText((Handle) itemHandle, stringData);
  847.     SetItemProc(dialog, kSizeSlider, DrawSlider);
  848.  
  849.     /* setup hardware settings label */
  850.  
  851.     SoundSettings2String(&globals->hardwareInfo, stringData);
  852.     itemHandle = GetItemHandle(dialog, kHardwareSettingsLabel);
  853.     SetIText((Handle) itemHandle, stringData);
  854.  
  855.     /* setup sound settings label */
  856.  
  857.     SoundSettings2String(&globals->sndInfo, stringData);
  858.     itemHandle = GetItemHandle(dialog, kSoundSettingsLabel);
  859.     SetIText((Handle) itemHandle, stringData);
  860.  
  861.     /* setup linear interpolation checkbox */
  862.  
  863.     SetCheckBox(dialog, kLerpCheckBox, 1);
  864.  
  865.     /* set the default button */
  866.  
  867.     SetDialogDefaultItem(dialog, kQuitButton);
  868. }
  869.  
  870. void SoundSettings2String(SoundComponentData *sndInfo, Str255 stringData)
  871. {
  872.     float        rate;
  873.     Str255        dataFormat;
  874.     OSErr        err;
  875.  
  876.     if ((sndInfo->format == kOffsetBinary) || 
  877.         (sndInfo->format == kTwosComplement))
  878.     {
  879.         sprintf((char *) dataFormat, "%d-bit", sndInfo->sampleSize);
  880.     }
  881.     else
  882.     {
  883.         err = GetCompressionName(sndInfo->format, dataFormat);
  884.         if (err != noErr)
  885.             sprintf((char *) dataFormat, "Unknown");
  886.         else
  887.             p2cstr(dataFormat);
  888.     }
  889.  
  890.     rate = ((double) sndInfo->sampleRate) / (65536.0 * 1000.0);
  891.  
  892.     sprintf((char *) stringData, "%s, %s, %5.3f kHz",
  893.             dataFormat,
  894.             (sndInfo->numChannels == 1) ? "mono" : "stereo",
  895.             rate);    
  896.     c2pstr((char *) stringData);
  897. }
  898.  
  899. void GetThumbRect(GlobalsPtr globals, short item, Rect *bounds, Rect *thumbRect)
  900. {
  901.     Rect    r;
  902.  
  903.     r = *bounds;
  904.     InsetRect(&r, 4, 0);
  905.  
  906.     switch (item)
  907.     {
  908.         case kLeftVolumeSlider:
  909.             {
  910.                 unsigned long    volume = globals->leftVolume;
  911.                 if (volume > kVolumeSliderMax)
  912.                     volume = kVolumeSliderMax;
  913.         
  914.                 r.left = r.right - 4 - ((kVolumeSliderMax - volume) * (r.right - r.left)) / kVolumeSliderMax;
  915.             }
  916.             break;
  917.  
  918.         case kRightVolumeSlider:
  919.             {
  920.                 unsigned long    volume = globals->rightVolume;
  921.                 if (volume > kVolumeSliderMax)
  922.                     volume = kVolumeSliderMax;
  923.         
  924.                 r.left = r.right - 4 - ((kVolumeSliderMax - volume) * (r.right - r.left)) / kVolumeSliderMax;
  925.             }
  926.             break;
  927.  
  928.         case kRateSlider:
  929.             {
  930.                 UnsignedFixed    rateMultiplier = globals->rateMultiplier;
  931.                 if (rateMultiplier > kRateSliderMax)
  932.                     rateMultiplier = kRateSliderMax;
  933.         
  934.                 r.left = r.right - 4 - ((kRateSliderMax - rateMultiplier) * (r.right - r.left)) / kRateSliderMax;
  935.             }
  936.             break;
  937.  
  938.         case kSizeSlider:
  939.             {
  940.                 unsigned long    chunkSamples = globals->chunkSamples;
  941.                 if (chunkSamples > globals->totalSamples)
  942.                     chunkSamples = globals->totalSamples;
  943.         
  944.                 r.left = r.right - 4 - ((globals->totalSamples - chunkSamples) * (r.right - r.left)) / globals->totalSamples;
  945.             } 
  946.             break;
  947.     }
  948.  
  949.     r.right = r.left + 8;
  950.     *thumbRect = r;
  951. }
  952.  
  953. pascal void DrawSlider(WindowPtr dialog, short item)
  954. {
  955.     UpdateSlider(&myGlobals, dialog, item);
  956. }
  957.  
  958. void UpdateSlider(GlobalsPtr globals, DialogPtr dialog, short item)
  959. {
  960.     Rect    r, bounds;
  961.     
  962.     GetItemRect(dialog, item, &bounds);
  963.     EraseRect(&bounds);
  964.  
  965.     r = bounds;
  966.     InsetRect(&r, 4, 4);
  967.     FrameRect(&r);
  968.     InsetRect(&r, 1, 1);
  969.     FillRect(&r, &qd.gray);
  970.  
  971.     GetThumbRect(globals, item, &bounds, &r);
  972.  
  973.     EraseRect(&r);
  974.     FrameRect(&r);
  975. }
  976.  
  977. void DragSlider(GlobalsPtr globals, DialogPtr dialog, short item)
  978. {
  979.     Rect            r, bounds, thumbRect;
  980.     Point            oldMouse, newMouse;
  981.     ControlHandle    itemHandle;
  982.     Str255            stringData;
  983.  
  984.     if (!StillDown())
  985.         return;
  986.  
  987.     GetItemRect(dialog, item, &bounds);
  988.  
  989.     r = bounds;
  990.     InsetRect(&r, 4, 4);
  991.     itemHandle = GetItemHandle(dialog, item + 1);
  992.  
  993.     GetThumbRect(globals, item, &bounds, &thumbRect);
  994.     oldMouse.h = (thumbRect.left + thumbRect.right) / 2;
  995.     oldMouse.v = (thumbRect.top + thumbRect.bottom) / 2;
  996.  
  997.      while (WaitMouseUp())
  998.     {
  999.         GetMouse(&newMouse);
  1000.         
  1001.         if (newMouse.h < r.left)
  1002.             newMouse.h = r.left;
  1003.         if (newMouse.h > r.right)
  1004.             newMouse.h = r.right;
  1005.  
  1006.         if (EqualPt(newMouse, oldMouse))
  1007.             continue;
  1008.         
  1009.         switch (item)
  1010.         {
  1011.             case kLeftVolumeSlider:
  1012.             case kRightVolumeSlider:
  1013.                 {
  1014.                     unsigned long volume = ((newMouse.h - r.left) * kVolumeSliderMax) / (r.right - r.left);
  1015.                     sprintf((char *) stringData, "%7.5f", ((float) volume) / 256.0);
  1016.                     c2pstr((char *) stringData);
  1017.                     
  1018.                     if (item == kLeftVolumeSlider)
  1019.                         globals->leftVolume = volume;
  1020.                     else
  1021.                         globals->rightVolume = volume;
  1022.  
  1023.                     SetVolume(globals->sndChannel, globals->leftVolume, globals->rightVolume);                    
  1024.                 }
  1025.                 break;
  1026.  
  1027.             case kRateSlider:
  1028.                 {
  1029.                     globals->rateMultiplier = ((newMouse.h - r.left) * kRateSliderMax) / (r.right - r.left);
  1030.                     sprintf((char *) stringData, "%7.5f", ((float) globals->rateMultiplier) / 65536.0);
  1031.                     c2pstr((char *) stringData);
  1032.                     SetRateMultiplier(globals->sndChannel, globals->rateMultiplier);                    
  1033.                 }
  1034.                 break;
  1035.  
  1036.             case kSizeSlider:
  1037.                 {
  1038.                     unsigned long chunkSamples = ((newMouse.h - r.left) * globals->totalSamples) / (r.right - r.left);
  1039.                     chunkSamples = (chunkSamples / globals->compInfo.samplesPerPacket) * globals->compInfo.samplesPerPacket;
  1040.                     if (chunkSamples < globals->minChunkSamples)
  1041.                         chunkSamples = globals->minChunkSamples;
  1042.                     globals->chunkSamples = chunkSamples;                // change value all at once 'cause it's used at interrupt time
  1043.                     NumToString(globals->chunkSamples, stringData);
  1044.                 }
  1045.                 break;
  1046.         }
  1047.  
  1048.         EraseRect(&thumbRect);
  1049.         FrameRect(&r);
  1050.         InsetRect(&r, 1, 1);
  1051.         FillRect(&r, &qd.gray);
  1052.         InsetRect(&r, -1, -1);
  1053.  
  1054.         GetThumbRect(globals, item, &bounds, &thumbRect);
  1055.         EraseRect(&thumbRect);
  1056.         FrameRect(&thumbRect);
  1057.  
  1058.         SetIText((Handle) itemHandle, stringData);
  1059.  
  1060.         oldMouse = newMouse;
  1061.     }
  1062. }
  1063.  
  1064. // Here are some useful routines for dealing with dialogs
  1065.  
  1066. ControlHandle GetItemHandle(DialogPtr dialog, short item)
  1067. {
  1068.     short         iKind;
  1069.     Rect         iRect;
  1070.     Handle         iHandle;
  1071.  
  1072.     GetDItem(dialog, item, &iKind, &iHandle, &iRect);
  1073.     return ((ControlHandle)iHandle);
  1074. }
  1075.  
  1076. void SetItemProc(DialogPtr dialog, short item, UserItemUPP proc)
  1077. {
  1078.     short         iKind;
  1079.     Rect         iRect;
  1080.     Handle         iHandle;
  1081.  
  1082.     GetDItem(dialog, item, &iKind, &iHandle, &iRect);
  1083.     SetDItem(dialog, item, iKind, (Handle) proc, &iRect);
  1084. }
  1085.  
  1086. void GetItemRect(DialogPtr dialog, short item, Rect *iRect)
  1087. {
  1088.     short         iKind;
  1089.     Handle         iHandle;
  1090.  
  1091.     GetDItem(dialog, item, &iKind, &iHandle, iRect);
  1092. }
  1093.  
  1094. short GetCheckBox(DialogPtr dialog, short item)
  1095. {
  1096.     short         iKind;
  1097.     Rect         iRect;
  1098.     Handle         iHandle;
  1099.  
  1100.     GetDItem(dialog, item, &iKind, &iHandle, &iRect);
  1101.     return (GetCtlValue((ControlHandle) iHandle));
  1102. }
  1103.  
  1104. void SetCheckBox(DialogPtr dialog, short item, short value)
  1105. {
  1106.     short         iKind;
  1107.     Rect         iRect;
  1108.     Handle         iHandle;
  1109.  
  1110.     GetDItem(dialog, item, &iKind, &iHandle, &iRect);
  1111.     SetCtlValue((ControlHandle) iHandle, (value) ? 1 : 0);
  1112. }
  1113.